package com.ls.lspicturebackend.manger.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.ls.lspicturebackend.manger.websocket.disruptor.PictureEditEventProducer;
import com.ls.lspicturebackend.manger.websocket.model.*;
import com.ls.lspicturebackend.model.entity.User;
import com.ls.lspicturebackend.service.UserService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
public class PictureEditHandler extends TextWebSocketHandler {

    @Resource
    private UserService userService;

    @Resource
    private PictureEditEventProducer pictureEditEventProducer;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private final String PICTURE_EDIT_KEY = "picture:edit:";

    // 保存当前图片是谁在编辑
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    // 保存当前图片的所有连接
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        pictureSessions.get(pictureId).add(session);

        // 构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("%s 加入编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userService.getUserVO(user));
        // 获取前置操作
        String cacheValue = stringRedisTemplate.opsForValue().get(PICTURE_EDIT_KEY + pictureId);
        if (StrUtil.isNotBlank(cacheValue)) {
            PreEditActions preEditActions = JSONUtil.toBean(cacheValue, PreEditActions.class);
            pictureEditResponseMessage.setPreZoomCount(preEditActions.getZoomInCount() - preEditActions.getZoomOutCount());
            pictureEditResponseMessage.setPreRotateCount(preEditActions.getRotateRightCount() - preEditActions.getRotateLeftCount());
        }
        // 获取当前编辑用户名
        Long curEditUserId = pictureEditingUsers.get(pictureId);
        if (ObjUtil.isNotNull(curEditUserId)) {
            Set<WebSocketSession> webSocketSessionSet = pictureSessions.get(pictureId);
            for (WebSocketSession webSocketSession : webSocketSessionSet) {
                if (curEditUserId.equals(webSocketSession.getAttributes().get("userId"))) {
                    User curEditUser = userService.getById(curEditUserId);
                    pictureEditResponseMessage.setCurEditingUser(userService.getUserVO(curEditUser));
                    break;
                }
            }
        }
        broadcastMessage(pictureId, pictureEditResponseMessage);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        // 将消息解析成PictureEditResponseMessage对象
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        Long pictureId = (Long) attributes.get("pictureId");
        User user = (User) attributes.get("user");
        // 移除当前用户的编辑状态
        handleExitEditMessage(pictureId, null, user, session);

        // 删除会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (sessionSet != null) {
            sessionSet.remove(session);
            if (sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
                stringRedisTemplate.delete(PICTURE_EDIT_KEY + pictureId);
            }
        }

        // 响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("%s离开编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userService.getUserVO(user));
        broadcastMessage(pictureId, pictureEditResponseMessage);
    }

    public void handleEnterEditMessage(Long pictureId, PictureEditRequestMessage pictureEditRequestMessage, User user, WebSocketSession session) throws Exception {
        // 没有用户正在编辑该图片，才能进入编辑
        if (!pictureEditingUsers.containsKey(pictureId)) {
            // 设置当前用户为编辑用户
            pictureEditingUsers.put(pictureId, user.getId());
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            String message = String.format("%s开始编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastMessage(pictureId, pictureEditResponseMessage);
        }
    }


    public void handleEditActionMessage(Long pictureId, PictureEditRequestMessage pictureEditRequestMessage, User user, WebSocketSession session) throws Exception {
        Long editingUserId = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        if (actionEnum == null) {
            return;
        }
        // 确认是当前编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            String message = String.format("%s执行%s", user.getUserName(), actionEnum.getText());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setEditAction(editAction);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            updatePreEditActions(pictureId, editAction);
            // 广播给除了当前客户端之外的其他用户，否则会造成重复编辑
            broadcastMessage(pictureId, pictureEditResponseMessage, session);
        }
    }

    private void updatePreEditActions(Long pictureId, String actionValue) {
        String cacheValue = stringRedisTemplate.opsForValue().get(PICTURE_EDIT_KEY + pictureId);
        PreEditActions preEditActions = new PreEditActions();
        if (StrUtil.isNotBlank(cacheValue)) {
            preEditActions = JSONUtil.toBean(cacheValue, PreEditActions.class);
        }

        switch (actionValue) {
            case "ZOOM_IN":
                preEditActions.setZoomInCount(preEditActions.getZoomInCount() + 1);
                break;
            case "ZOOM_OUT":
                preEditActions.setZoomOutCount(preEditActions.getZoomOutCount() + 1);
                break;
            case "ROTATE_LEFT":
                preEditActions.setRotateLeftCount(preEditActions.getRotateLeftCount() + 1);
                break;
            case "ROTATE_RIGHT":
                preEditActions.setRotateRightCount(preEditActions.getRotateRightCount() + 1);
                break;
            default:
                break;
        }
        stringRedisTemplate.opsForValue().set(PICTURE_EDIT_KEY + pictureId, JSONUtil.toJsonStr(preEditActions), 60, TimeUnit.MINUTES);
    }


    public void handleExitEditMessage(Long pictureId, PictureEditRequestMessage pictureEditRequestMessage, User user, WebSocketSession session) throws Exception {
        Long editingUserId = pictureEditingUsers.get(pictureId);
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除当前用户的编辑状态
            pictureEditingUsers.remove(pictureId);
            // 构造响应，发送退出编辑的消息通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("%s退出编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userService.getUserVO(user));
            broadcastMessage(pictureId, pictureEditResponseMessage);
        }
    }


    // 广播信息
    private void broadcastMessage(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws IOException {
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if (CollUtil.isNotEmpty(webSocketSessions)) {
            // 将PictureEditResponseMessage里面的数据转化为Json格式
            // 注意要将Long数据类型转化为String，防止前端精度丢失
            ObjectMapper objectMapper = new ObjectMapper();
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            for (WebSocketSession webSocketSession : webSocketSessions) {
                if (excludeSession != null && webSocketSession.equals(excludeSession)) {
                    continue;
                }
                if (webSocketSession.isOpen()) {
                    webSocketSession.sendMessage(textMessage);
                }
            }
        }
    }

    private void broadcastMessage(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws IOException {
        broadcastMessage(pictureId, pictureEditResponseMessage, null);
    }

}
